<?php if(!defined("QG_PATH")){die("Direct file access not allowed");}
/**
 * QuickGraph Class
*/
class QuickGraph {
	
	/* Titles */
	protected $title = false;		// Chart title.  If false, no title.
	protected $title_size = 10;		// Title Font Size
	protected $axis_title_size = 8;	// Axis Title Font Size
	protected $axis_label_size = 7;	// Axis Label Font Size
	
	/* Y Axis Variables */
	protected $yauto = true; 		// Automatically create Y axis scale from data?
	protected $ymin = false; 		// If yauto is false, ymin and ymax specify the linear Y axis scale range.
	protected $ymax = false; 		//  As ymin
	protected $yaxis = false; 		// If ymin and ymax are false, yaxis labels are used. Labels spaced equally.
	protected $yaxisticks = true;	// Number of labels on y axis
	
	/* X Axis Variables */
	protected $xauto = true;		// Automatically create X axis scale from data?
	protected $xmin = false;		// If xauto is false, xmin and xmax specify the linear X axis scale range.
	protected $xmax = false;		// As xmin
	protected $xaxis = false; 		// Array of X axis labels.
	protected $xaxisticks = true;	// Number of labels on x axis
	
	/* Scales */
	protected $scales = array(
		'xtitle' => false,
		'ytitle' => false,
		'x' => array(),
		'y' => array(),
		'xwidth' => 0,
		'ywidth' => 0,
		'max' => 0,
		'ymax' => 0,
		'ymin' => 0
	);
	
	/* Current graphing area bounding box offsets. */
	protected $xoff_l = 0;
	protected $xoff_r = 0;
	protected $yoff_t = 0;
	protected $yoff_b = 0;
	
	/* Configuration Variables */
	protected $error_reporting = true;	// Echo errors?
	protected $width = 600;			// width of the whole image
	protected $height = 400;			// height of the whole image
	protected $border = 10;			// pixel width of border
	protected $bgcol = array(255,255,255); // Background color for whole canvas.
	protected $axcol = array(0,0,0);	// Axis Color
	protected $titlescol = array(0,0,0); // Title Color
	protected $axthick = 1;			// Axis Tickness
	protected $font = "";				// Full path to the current TrueType font
	protected $chart_style = 1;			// Line chart or bar chart style axis? 1 = line, 0 = bar
	
	/* The Data Array */
	protected $data = array();			// holds the data
	
	/* Internal Variables */
	protected $image = false;			// The Image
	protected $generated = false;		// Has the graph been generated?
	
	/**
	 * Construct function.
	 * Create a true color image, set antialiasing and create the background and axis colors.
	 */
	public function __construct ($width,$height)
	{
		$this->width = $width;
		$this->height = $height;
		$this->image = imagecreatetruecolor($this->width,$this->height);
		$this->font = QG_PATH."fonts/verdana.ttf";
		imageantialias($this->image,true);
		$this->set_background_color($this->bgcol);
		//$this->axcol = imagecolorallocate($this->image,$this->axcol[0],$this->axcol[1],$this->axcol[2]);
		$this->titlescol = imagecolorallocate($this->image,$this->titlescol[0],$this->titlescol[1],$this->titlescol[2]);
	}
	
	/**
	* addSeries
	* Adds a series to the data array.
	*/
	public function addSeries ( $data = array(), $color=array(255,0,0), $name=false, $params=false )
	{
		if ( is_array($data) && count($data) > 0 )
		{
			$color = $this->color_check($color);
			$num = count($this->data);
			$this->data[$num]['name'] = (( $name ) ? $name : "Series ".($num+1));
			$this->data[$num]['data'] = $data;
			$this->data[$num]['color'] = $color; //imagecolorallocate($this->image,$color[0],$color[1],$color[2]);
			$this->data[$num]['width'] = 1;
			if ( is_array($params) )
			{
				if ( isset($params['thickness']) && is_numeric($params['thickness']) )
				{
					$this->data[$num]['width'] = $params['thickness'];
				}
			}
			return true;
		}
		else
		{
			return false;
		}
	}
	
	/**
	 * generate function
	 * Sets the generated variable to true so we know no more config functions can be called.
	 * Fills the image with the background color, calculates the scales and then calls the draw
	 * function that should be defined in the graph type class.
	 */
	public function generate()
	{
		$this->generated = true;
		//var_dump($this->data);
		imagefilledrectangle($this->image,0,0,$this->width,$this->height,$this->bgcol);
		$this->xoff_l = $this->border;
		$this->xoff_r = $this->border;
		$this->yoff_t = $this->border;
		$this->yoff_b = $this->border;
		$this->calculate_scales();
		$this->draw_titles ();
		$this->draw();
	}
	
	/**
	 * calculate_scales Function
	 * Does all the calculations to set the values for each tick on the axis.
	 */
	protected function calculate_scales()
	{
		/* X Axis
		 * If no xaxis scale has been specified then simple 1,2,3,4,5 etc will be used.
		 * If there are labels in the array then each label will be assigned to each item in order.
		 * If there is a range then the difference will be divided by number of items and scale calculated.
		 */
		$max = 0;
		foreach  ( $this->data as $val ) // Find maximum number of data items in the series'
		{
			$max = (( $max < count($val['data']) ) ? count($val['data']) : $max );
			$this->scales['max'] = $max;
		}
		if ( $this->xaxisticks === true )
		{
			$this->xaxisticks = $max;
		}
		if ( $this->yaxisticks === true )
		{
			$this->yaxisticks = 10;
		}
		if ( $this->xauto ) // What auto can there really be for an x axis? Just incrementing numbers...
		{
			for ( $i = 0; $i < $max; $i++)
			{
				$this->scales['x'][$i] = $i;
			}
		}
		elseif ( is_numeric($this->xmin) && is_numeric($this->xmax) && $this->xmax > $this->xmin ) // Ok, we have a range - we need to interp between the min and max...
		{
			$interval = ($this->xmax - $this->xmin)/$this->xaxisticks;
			for ( $i = 0; $i <= $this->xaxisticks; $i++ )
			{
				$this->scales['x'][$i] = $this->xmin + ($interval*$i);
			}
		}
		elseif ( $this->xaxis && is_array($this->xaxis) ) // Array of labels specified
		{
			for ( $i = 0; $i < $max; $i++ )
			{
				$this->scales['x'][$i] = ((isset($this->xaxis[$i])) ? $this->xaxis[$i] : ($i));
			}
		}
		else
		{
			$this->error("Calculate Scales Error: (X Axis) Auto-scale is off and no min/max or labels are specified. Can not calculate scales");
			exit;
		}
		 /* Y Axis
		  * If auto - get min and max from data then interp.
		  * If not auto & min/max - interp between them.
		  * If not auto or min/max - evenly space labels.
		  */
		$max = false; $min = false;
		foreach ( $this->data as $val ) {
			$min = ((!$min||min($val['data'])<$min) ? min($val['data']) : $min );
			$max = ((!$max||max($val['data'])>$max) ? max($val['data']) : $max );
		}
		if ( $this->yauto ) // automatically calculate the range
		{
			$this->scales['ymax'] = $max;
			$this->scales['ymin'] = $min;
			$interval = ($max - $min)/$this->yaxisticks;
			for ( $i = 0; $i <= $this->yaxisticks; $i++ )
			{
				$this->scales['y'][$i] = $min + ($interval*$i);
			}
		}
		elseif ( is_numeric($this->ymin) && is_numeric($this->ymax) && $this->ymax > $this->ymin )
		{
			$this->scales['ymax'] = $this->ymax;
			$this->scales['ymin'] = $this->ymin;
			$interval = ($this->ymax - $this->ymin)/$this->yaxisticks;
			for ( $i = 0; $i <= $this->yaxisticks; $i++ )
			{
				$this->scales['y'][$i] = $this->ymin + ($interval*$i);
			}
		}
		elseif ( $this->yaxis && is_array($this->yaxis) )
		{ 
			$max = 0;
			foreach  ( $this->data as $val )
			{
				$max = (( $max < count($val['data']) ) ? count($val['data']) : $max );
			}
			for ( $i = 0; $i < $max; $i++ )
			{
				$this->scales['y'][$i] = ((isset($this->yaxis[$i])) ? $this->yaxis[$i] : ($i+1));
			}
		}
		else
		{
			$this->error("Auto-scale is off and no min/max or labels are specified. Can not calculate scales");
			exit;
		}
	}
	
	/**
	 * draw_titles Function
	 * Draws the graph title
	 */
	protected function draw_titles ()
	{
		if ( $this->title )
		{
			$co = $this->co($this->center_text($this->title_size,$this->font,$this->title),0);
			$a = $this->draw_text($this->title_size,$co[0],$co[1],$this->titlescol,$this->font,$this->title);
			$this->yoff_t = $this->yoff_t + $a[1] + 2;
		}
	}
	
	/**
	 * draw_text Function
	 * Function for drawing True Type text
	 * imagettftext() using bottom left corner co-ords, this function takes top left corner and converts, unless $pos is false.
	 */
	protected function draw_text ($size,$x,$y,$col,$font,$text,$pos=true,$deg=0)
	{
		$b = imagettfbbox($size,$deg,$font,$text);
		if ( $pos )
		{
			if ( $deg == 0 )
			{
				$x = $x-$b[6];
				$y = $y-$b[7];
			}
			else
			{
				$x = $x-($b[4]-$b[2]);
				$y = $y;
			}
		}
		imagettftext($this->image,$size,$deg,$x,$y,$col,$font,$text);
		return array($x,$y,$b[2],abs($b[5]));
	}
	
	/**
	 * center_text Function
	 * Returns the left x coordinate to center the specified string between the current left and right x offsets, relative to the current offsets.
	 */
	protected function center_text ( $size,$font,$text,$a=false,$dir="h" )
	{
		if ( $dir == "h" )
		{
			$b = imagettfbbox($size,0,$font,$text);
			if ( $a === false )
			{
				return (($this->width-$this->xoff_l-$this->xoff_r)/2)-($b[2]/2); //-$this->xoff_l;
			}
			return ($a-($b[2]/2));
		}
		elseif ( $dir == "v" )
		{
			$b = imagettfbbox($size,90,$font,$text);
			//var_dump($b);
			if ( $a === false )
			{
				//echo (($b[1]-$b[3]));
				return (($this->height-$this->yoff_t-$this->yoff_b)/2)-(($b[1]-$b[3])/2);
			}
			return ($a-(($b[2]-$b[4])/2));
		}
	}
	
	/**
	 * draw_axis Function
	 * Draws the x and y axis and their labels.
	 */
	protected function draw_axis ()
	{
		/* Height of a line of text */
		$a = imagettfbbox($this->axis_title_size,0,$this->font,"Tj");
		$h = $a[3]-$a[5];
		$a = imagettfbbox($this->axis_label_size,0,$this->font,"Tj");
		$l = $a[3]-$a[5];
		/* Set the offsets */
		$this->yoff_b = $this->yoff_b+$h+$l;
		$this->xoff_l = $this->xoff_l+$h+$l;
		/* Draw the axis */
		$s = $this->co(0,0,"bl");
		$f = $this->co(0,0,"br");
		$this->line($s[0],$s[1],$f[0],$f[1],$this->axcol,$this->axthick);
		$this->scales['xwidth'] = $f[0]-$s[0];
		//$this->line($s[0]+(($f[0]-$s[0])/2),$this->height,$s[0]+(($f[0]-$s[0])/2),0,array(255,0,255),1);
		$s = $this->co(0,0,"bl");
		$f = $this->co(0,0,"tl");
		$this->line($s[0],$s[1],$f[0],$f[1],$this->axcol,$this->axthick);
		$this->scales['ywidth'] = $s[1]-$f[1];
		//$this->line(0,$s[1]+(($f[1]-$s[1])/2),$this->width,$s[1]+(($f[1]-$s[1])/2),array(255,0,255),1);
		/* Reset the y offset so X text is at right height
		 * Keep the x offset though, so text is centered correctly.
		*/
		$this->yoff_b = $this->yoff_b-$h-$l;
		/* X Axis Title */
		$co = $this->co($this->center_text($this->axis_title_size,$this->font,$this->scales['xtitle']),0,"bl");
		$a = $this->draw_text($this->axis_title_size,$co[0],$co[1],$this->axis_title_col,$this->font,$this->scales['xtitle'],false);
		$this->yoff_b = $this->yoff_b+$h;
		/* X Axis Labels */
		$tick = $this->scales['xwidth']/(count($this->scales['x'])-$this->chart_style);
		for ( $i = 0; $i < count($this->scales['x']); $i++ )
		{
			$a = $this->co($this->center_text($this->axis_label_size,$this->font,$this->scales['x'][$i],$tick*$i),0,"bl");
			$this->draw_text($this->axis_label_size,$a[0],$a[1],$this->axis_title_col,$this->font,$this->scales['x'][$i],false);
		}
		/* Reset the x offset to Y text is at right height
		 * Reset the y offset so text is centered correctly.
		 */
		$this->xoff_l = $this->xoff_l-$h-$l;
		$this->yoff_b = $this->yoff_b+$l;
		/* Y Axis Title */
		$co = $this->co(0,$this->center_text($this->axis_title_size,$this->font,$this->scales['ytitle'],false,'v'),"bl");
		$a = $this->draw_text($this->axis_title_size,$co[0],$co[1],$this->axis_title_col,$this->font,$this->scales['ytitle'],true,90);
		$this->xoff_l = $this->xoff_l+$h;
		/* Y Axis Labels */
		$tick = $this->scales['ywidth']/(count($this->scales['y'])-$this->chart_style);
		for ( $i = 0; $i < count($this->scales['y']); $i++ )
		{
			$a = $this->co(0,$this->center_text($this->axis_label_size,$this->font,$this->scales['y'][$i],$tick*$i),"bl");
			$this->draw_text($this->axis_label_size,$a[0],$a[1],$this->axis_title_col,$this->font,$this->scales['y'][$i],true,90);
		}
		$this->xoff_l = $this->xoff_l+$l;
	}
	
	/**
	 * co Function
	 * Takes an x and y coordinate relative to the current offsets and returns the
	 * coordinates relative to (0,0)
	 * Can be calculated for x,y offset from any corner of the graph area (area within offsets)
	 */
	protected function co ( $x, $y, $corner="tl" )
	{
		switch ($corner) {
			case "tl":
				return array ($x+$this->xoff_l,$y+$this->yoff_t);
				break;
			case "bl":
				return array($x+$this->xoff_l,$this->height-$this->yoff_b-$y);
				break;
			case "tr":
				return array($this->width-$this->xoff_r-$x,$y+$this->yoff_t);
				break;
			case "br":
				return array($this->width-$this->xoff_r-$x,$this->height-$this->yoff_b-$y);
				break;
		}
	}
	
	protected function y_point ( $value )
	{
		if ( $value < $ymin || $value <= 0 ) {
			return 0;
		}
		$point = $this->scales['ywidth']*($value/($this->scales['ymax']-$this->scales['ymin']));
		//echo $point.",";
		return $point;
	}
	
	/**
	 * output Function
	 * Outputs the graph to the browser using $type specified.
	 */
	public function output ( $type = "png" )
	{
		if (!$type || $type == "png")
		{
			header("Content-type: image/png");
			imagepng($this->image);
			imagedestroy($this->image);
		}
	}
	
	/**
	 * polygon  Function
	 * Draws an antialiased polygon of the specified color.
	 */
	protected function polygon ( $points, $color, $alias = true )
	{
		if ( !is_array($points) )
		{
			$this->error("Points must be specified in an array, you gave: ".$this->typeof($points));
			return false;
		}
		if ( count($points) < 6 || is_int(count($points)/2) ) // needs to be 3 points or more and an even number.
		{
			$this->error("Must be more than 3 points and an even number of values.");
			return false;
		}
		$num_points = count($points)/2;
		$color = $this->color_check($color);
		$col = imagecolorallocate($this->image,$color[0],$color[1],$color[2]);
		imagefilledpolygon($this->image,$points,$num_points,$col);
		if ( $alias )
		{
			imagepolygon($this->image,$points,$num_points,$col);
		}
		return true;
	}
	
	/**
	 * line Function
	 * Draws a line of specified thickness.
	 */
	protected function line ( $x1, $y1, $x2, $y2, $color, $thick = 1 )
	{
		$color = $this->color_check($color);
		$color = imagecolorallocate($this->image,$color[0],$color[1],$color[2]);
		if ($thick == 1) {
			return imageline($this->image, $x1, $y1, $x2, $y2, $color);
		}
		$t = $thick / 2 - 0.5;
		if ($x1 == $x2 || $y1 == $y2) {
			return imagefilledrectangle($this->image, round(min($x1, $x2) - $t), round(min($y1, $y2) - $t), round(max($x1, $x2) + $t), round(max($y1, $y2) + $t), $color);
		}
		$k = ($y2 - $y1) / ($x2 - $x1); //y = kx + q
		$a = $t / sqrt(1 + pow($k, 2));
		$points = array(
			round($x1 - (1+$k)*$a), round($y1 + (1-$k)*$a),
			round($x1 - (1-$k)*$a), round($y1 - (1+$k)*$a),
			round($x2 + (1+$k)*$a), round($y2 - (1-$k)*$a),
			round($x2 + (1-$k)*$a), round($y2 + (1+$k)*$a),
		);
		imagefilledpolygon($this->image, $points, 4, $color);
		return imagepolygon($this->image, $points, 4, $color);
	}
	
	protected function color_check ( $color )
	{
		array_walk($color,create_function('&$a,$b','$a = (($a>-1) ? (($a<256) ? $a : "255" ) : "0");'));
		return $color;
	}
	/**
	 * set_* functions
	 * Used to set various variables in the class.
	 */
	
	public function set_error_reporting ( $error_reporting = true )
	{
		$this->error_reporting = $error_reporting;
	}
	
	public function set_xaxis_scale ( $min = false, $max = false )
	{
		if ( is_array($min) )
		{
			$this->xauto = false;
			$this->xaxis = $min;
		}
		elseif ( is_numeric($min) && is_numeric($max) ) 
		{
			$this->xauto = false;
			$this->xmin = $min;
			$this->xmax = $max;
		}
		else
		{
			$this->error("setXaxis takes either an array of labels or a minimum/maximum range.");
			return false;
		}
		return true;
	}
	
	public function set_yaxis_scale ( $min = false, $max = false )
	{
		if ( is_array($min) )
		{
			$this->yauto = false;
			$this->yaxis = $min;
		}
		elseif ( is_numeric($min) && is_numeric($max) ) 
		{
			$this->yauto = false;
			$this->ymin = $min;
			$this->ymax = $max;
		}
		else
		{
			$this->error("setYaxis takes either an array of labels or a minimum/maximum range.");
			return false;
		}
		return true;
	}
	public function set_yaxis_ticks ( $value )
	{
		if ( is_int($value) )
		{
			$this->yaxisticks = $value;
			return true;
		}
		$this->error("set_yaxis_ticks only takes an integer value. You gave: ".$this->typeof($value));
		exit;
	}
	public function set_xaxis_ticks ( $value )
	{
		if ( is_int($value) )
		{
			$this->xaxisticks = $value;
			return true;
		}
		$this->error("set_yaxis_ticks only takes an integer value. You gave: ".$this->typeof($value));
		exit;
	}
	public function set_axis_color ( $axcol )
	{
		$this->axcol = imagecolorallocate($this->image,$axcol[0],$axcol[1],$axcol[2]);
	}
	public function set_background_color ( $bgcol )
	{
		$this->bgcol = imagecolorallocate($this->image,$bgcol[0],$bgcol[1],$bgcol[2]);
	}
	public function set_title ( $text, $size = false )
	{
		$this->title = $text;
		if ( is_numeric($size) )
		{
			$this->title_size = $size;
		}
	}
	public function set_title_size ( $size )
	{
		if ( is_numeric($size) )
		{
			$this->title_size = $size;
			return true;
		}
		return false;
	}
	public function set_xtitle ( $text )
	{
		$this->scales['xtitle'] = $text;
	}
	public function set_ytitle ( $text )
	{
		$this->scales['ytitle'] = $text;
	}
	public function set_axis_width ( $width )
	{
		if ( is_numeric($width) )
		{
			$this->axthick = $width;
			return true;
		}
		return false;
	}
	public function set_font ( $font )
	{
		if ( file_exist(QG_PATH."fonts/".$font.".ttf") )
		{
			$this->font = QG_PATH."fonts/".$font.".ttf";
			return true;
		}
		return false;
	}
	public function set_chart_style ($val)
	{
		if ( $val == "line" )
		{
			$this->chart_style = 1;
		}
		else
		{
			$this->chart_style = 0;
		}
	}
	/**
	 * error Function
	 * If error_reporting is true echos the message.
	 */
	protected function error ($message)
	{
		if ($this->error_reporting)
		{
			echo $message;
		}
	}
	
	/**
	 * typeof Function
	 * Used in error messages to return full information on a variable.
	 */
	protected function typeof ($var)
	{
		ob_start();
		var_dump($var);
		$ob = ob_get_contents();
		ob_end_clean();
		return "<span style=\"font-family: courier new;\">".$ob."</span>";
	}
	
	/* draw Function
	 * This function is just to give an error that a draw function has not been defined in the graph type class.
	 */
	protected function draw()
	{
		$this->error("No draw function defined in loaded library. This is required.");
		exit;
	}
}
?>